﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Sencha.Serialization.Contracts;
using System.Web;
using System.Web.Mvc;
using Sencha.Direct.Api;

namespace Sencha.Direct
{
	public abstract class DirectController : AsyncController
	{
		protected static readonly ReadOnlyCollection<Type> DefaultKnownTypes;
		protected static readonly ModelBinderDictionary DefaultBinders;

		static DirectController()
		{
			DefaultKnownTypes = new ReadOnlyCollection<Type>(new Type[] { 
				typeof(Router.IncomingRequest[]),
				typeof(Router.OutgoingResponse[]),
				typeof(JsError[]),
				typeof(Api.ControllerDescriptionSerializer)
			});
		}
		public DirectController()
		{

		}

		protected override JsonResult Json(object data, string contentType, System.Text.Encoding contentEncoding, JsonRequestBehavior behavior)
		{
			return new DirectJsonResult()
			{
				Serializer = data != null ? this.CreateSerializer(data.GetType()) : null,
				Data = data,
				ContentEncoding = contentEncoding,
				ContentType = contentType,
				JsonRequestBehavior = behavior
			};
		}

		protected JsonResult TypedJson<T>(T data)
		{
			return this.TypedJson<T>(data, null, null, JsonRequestBehavior.DenyGet);
		}
		protected JsonResult TypedJson<T>(T data, bool compress)
		{
			return this.TypedJson<T>(data, null, null, JsonRequestBehavior.DenyGet, compress);
		}
		protected JsonResult TypedJson<T>(T data, JsonRequestBehavior behavior)
		{
			return this.TypedJson<T>(data, null, null, behavior);
		}
		protected JsonResult TypedJson<T>(T data, JsonRequestBehavior behavior, bool compress)
		{
			return this.TypedJson<T>(data, null, null, behavior, compress);
		}
		protected JsonResult TypedJson<T>(T data, string contentType)
		{
			return this.TypedJson<T>(data, contentType, null, JsonRequestBehavior.DenyGet);
		}
		protected JsonResult TypedJson<T>(T data, string contentType, bool compress)
		{
			return this.TypedJson<T>(data, contentType, null, JsonRequestBehavior.DenyGet, compress);
		}
		protected JsonResult TypedJson<T>(T data, string contentType, System.Text.Encoding contentEncoding)
		{
			return this.TypedJson<T>(data, contentType, contentEncoding, JsonRequestBehavior.DenyGet);
		}
		protected JsonResult TypedJson<T>(T data, string contentType, System.Text.Encoding contentEncoding, bool compress)
		{
			return this.TypedJson<T>(data, contentType, contentEncoding, JsonRequestBehavior.DenyGet, compress);
		}
		protected JsonResult TypedJson<T>(T data, string contentType, JsonRequestBehavior behavior)
		{
			return this.TypedJson<T>(data, contentType, null, behavior);
		}
		protected JsonResult TypedJson<T>(T data, string contentType, JsonRequestBehavior behavior, bool compress)
		{
			return this.TypedJson<T>(data, contentType, null, behavior, compress);
		}
		protected virtual JsonResult TypedJson<T>(T data, string contentType = null, System.Text.Encoding contentEncoding = null, JsonRequestBehavior behavior = JsonRequestBehavior.DenyGet, bool compress = false)
		{
			return new DirectJsonResult<T>
			{
				Serializer = data != null ? this.CreateSerializer(typeof(T)) : null,
				Data = data,
				ContentEncoding = contentEncoding,
				ContentType = contentType,
				JsonRequestBehavior = behavior,
				EnableCompression = compress
			};
		}

		protected JsonResult Records()
		{
			return new RecordsActionResult<object>(System.Linq.Enumerable.Empty<object>(), null, null, null) { Serializer = this.CreateSerializer(typeof(RecordsActionResult<object>.JsRecordsResponse)) };
		}
		protected JsonResult Records<T>(IEnumerable<T> items)
		{
			if (items == null)
				items = System.Linq.Enumerable.Empty<T>();

			return Records<T>(items, null, null, null);
		}
		protected JsonResult Records<T>(IEnumerable<T> items, int total)
		{
			if (items == null)
				items = System.Linq.Enumerable.Empty<T>();

			return Records<T>(items, null, null, total);
		}
		protected JsonResult Records<T>(IEnumerable<T> items, int start, int limit)
		{
			if (items == null)
				items = System.Linq.Enumerable.Empty<T>();

			return Records<T>(items, start, limit, null);
		}
		protected JsonResult Records<T>(IEnumerable<T> items, int? start, int? limit, int? total)
		{
			if (items == null)
				items = System.Linq.Enumerable.Empty<T>();

			return new RecordsActionResult<T>(items, start, limit, total) { Serializer = this.CreateSerializer(typeof(RecordsActionResult<T>.JsRecordsResponse)) };
		}

		protected JsonResult Success()
		{
			return new FormActionResult<object>() { Serializer = this.CreateSerializer(typeof(FormActionResult<object>.JsFormResponse)) };
		}
		protected JsonResult Form<T>(T data)
		{
			return new FormActionResult<T>(data) { Serializer = this.CreateSerializer(typeof(FormActionResult<T>.JsFormResponse)) };
		}
		protected JsonResult Form(ModelStateDictionary state)
		{
			if (state == null)
				throw new ArgumentNullException("state");

			if (state.IsValid)
				return Success();

			var errors = new List<JsError>(100);

			foreach (var name in state.Keys)
			{
				foreach (var error in state[name].Errors)
				{
					if (error.Exception != null)
						errors.Add(new JsError(error.Exception) { Name = name });
					else
						errors.Add(new JsError { Name = name, Message = error.ErrorMessage });
				}
			}

			var result = new FormActionResult<object>(errors) { Serializer = this.CreateSerializer(typeof(FormActionResult<object>.JsFormResponse)) };

			return result;
		}
        protected JsonResult Errors(IDictionary<string, Exception> errors)
        {
            if (errors == null)
                throw new ArgumentNullException("errors");

            var newErrors = new Dictionary<string, string>();
            foreach (var kv in errors)
                newErrors.Add(kv.Key, kv.Value.Message);

            return new FormActionResult<object>(newErrors);
        }
        protected JsonResult Errors(params JsError[] errors)
        {
            if (errors == null)
                throw new ArgumentNullException("errors");

            var newErrors = new Dictionary<string, string>();
            foreach (var kv in errors)
                newErrors.Add(kv.Name, kv.Message);

            return new FormActionResult<object>(newErrors);
        }
        protected JsonResult Errors(IDictionary<string, string> errors)
        {
            if (errors == null)
                throw new ArgumentNullException("errors");

            return new FormActionResult<object>(errors);
        }

		protected JsonResult Error()
		{
			return new ErrorActionResult();
		}
		protected JsonResult Error(string name, string message)
		{
			return new ErrorActionResult(name, message);
		}
		protected JsonResult Error(Exception exc)
		{
			return new ErrorActionResult(exc);
		}
		protected JsonResult Error(JsError error)
		{
			if (error == null)
				throw new ArgumentNullException("error");

			return new ErrorActionResult(error);
		}


		/// <summary>
		/// Generated API for this Controller
		/// </summary>
		/// <returns></returns>
		[NonDirectAction]
		public JsonResult Api()
		{
			var description = DescriptionCache.GetControllerDescription(this.GetType());
			var url = this.Url.Action("Route", "Router", (object)null);

			var api = new Dictionary<string, object>
			{
				{"url", url },
				{"type", "rpc" },
				{"namespace", description.Namespace },
				{"actions", description }
			};
			return TypedJson(api);
		}

		protected virtual Type[] GetKnownTypesForSerializer()
		{
			return Type.EmptyTypes;
		}
		protected internal virtual XmlObjectSerializer CreateSerializer(Type forType)
		{
			var cacheKey = forType;
			var contract = (DataContract)null;

			contract = DataContractCache.FetchOrCreate(cacheKey, () =>
			{
				var knownTypes = GetKnownTypesForSerializer();
				var defaultTypes = DefaultKnownTypes;

				var interp = MetadataInterpreter.DataContract;
				var descr = MetadataDescriber.ReflectionPublicOnly;

				contract = new DataContract(forType, knownTypes, interp, descr);				
				contract.AddKnownTypes(defaultTypes);				

				return contract;
			});

			var serializer = new Serialization.JsonSerializer(contract);
			return serializer;
		}
	}
}
